﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;
using System.IO;
using System.Windows.Media;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using SkeletalAnimation3D;

namespace Animator3D.Importers
{
    public class ObjImporter
    {
        private struct FacePoint
        {
            public int Point;
            public int Normal;
            public int TextureCoordinate;

            public FacePoint(int point, int normal, int textureCoordinate)
            {
                Point = point;
                Normal = normal;
                TextureCoordinate = textureCoordinate;
            }
        }

        private class Face : List<FacePoint> { }

        private class FaceCollection : List<Face> { }

        //TODO: allow scale ratio to be entered.
        public static BoneAnimationLibrary Read(Stream stream, bool center)
        {
            Point3DCollection positions = new Point3DCollection();
            PointCollection textureCoordinates = new PointCollection();
            Vector3DCollection normals = new Vector3DCollection();
            FaceCollection faces = new FaceCollection();

            using (StreamReader reader = new StreamReader(stream))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    line = line.Trim();
                    if (line.Length > 0)
                    {
                        if (line[0] == '#')
                        {
                            //comment
                        }
                        else
                        {
                            int spaceIndex = line.IndexOf(' ');
                            if (spaceIndex != -1)
                            {
                                string cmdToken = line.Substring(0, spaceIndex);
                                string args = line.Substring(spaceIndex + 1);
                                switch (cmdToken.ToUpper())
                                {
                                    case "V":
                                        positions.Add(ParsePoint3D(args));
                                        break;
                                    case "VT":
                                        textureCoordinates.Add(ParsePoint(args));
                                        break;
                                    case "VN":
                                        normals.Add(ParseVector3D(args));
                                        break;
                                    case "F":
                                        faces.Add(ParseFace(args));
                                        break;
                                }
                            }
                        }
                    }
                }
            }

            Point3DCollection adjustedPositions = new Point3DCollection();
            PointCollection adjustedTextureCoordinates = new PointCollection();
            Vector3DCollection adjustedNormals = new Vector3DCollection();

            if (center && positions.Count > 0)
            {
                Point3D minimum = positions[0], maximum = positions[0];
                for (int p = 1; p < positions.Count; p++)
                {
                    minimum.X = Math.Min(minimum.X, positions[p].X);
                    minimum.Y = Math.Min(minimum.Y, positions[p].Y);
                    minimum.Z = Math.Min(minimum.Z, positions[p].Z);

                    maximum.X = Math.Max(maximum.X, positions[p].X);
                    maximum.Y = Math.Max(maximum.Y, positions[p].Y);
                    maximum.Z = Math.Max(maximum.Z, positions[p].Z);
                }

                Point3D centerPoint = new Point3D((maximum.X + minimum.X) / 2, (maximum.Y + minimum.Y) / 2, (maximum.Z + minimum.Z) / 2);
                for (int p = 0; p < positions.Count; p++)
                {
                    positions[p] = new Point3D(positions[p].X - centerPoint.X, positions[p].Y - centerPoint.Y, positions[p].Z - centerPoint.Z);
                }

            }
            for (int f = 0; f < faces.Count; f++)
            {
                Face face = faces[f];
                int c1 = 0;
                int c2 = 1;
                for (int c3 = 2; c3 < face.Count; c3++)
                {
                    adjustedPositions.Add(positions[face[c1].Point - 1]);
                    adjustedNormals.Add(normals[face[c1].Normal - 1]);
                    adjustedTextureCoordinates.Add(textureCoordinates[face[c1].TextureCoordinate - 1]);

                    adjustedPositions.Add(positions[face[c2].Point - 1]);
                    adjustedNormals.Add(normals[face[c2].Normal - 1]);
                    adjustedTextureCoordinates.Add(textureCoordinates[face[c2].TextureCoordinate - 1]);

                    adjustedPositions.Add(positions[face[c3].Point - 1]);
                    adjustedNormals.Add(normals[face[c3].Normal - 1]);
                    adjustedTextureCoordinates.Add(textureCoordinates[face[c3].TextureCoordinate - 1]);

                    c2 = c3;
                }
            }
            BoneAnimationLibrary animationLibrary = new BoneAnimationLibrary(adjustedPositions, adjustedNormals, adjustedTextureCoordinates);
            return animationLibrary;
        }

        private static Face ParseFace(string args)
        {
            Face face = new Face();
            string[] corners = args.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            for (int c = 0; c < corners.Length; c++)
            {
                FacePoint facePoint = ParseFacePoint(corners[c]);
                face.Add(facePoint);
            }
            return face;
        }

        private static FacePoint ParseFacePoint(string args)
        {
            int position, normal = -1, textureCoordinate = -1;
            string[] indices = args.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            int.TryParse(indices[0], out position);
            if (indices.Length > 1 && !String.IsNullOrEmpty(indices[1]))
            {
                int.TryParse(indices[1], out textureCoordinate);
            }
            if (indices.Length > 2 && !String.IsNullOrEmpty(indices[2]))
            {
                int.TryParse(indices[2], out normal);
            }
            return new FacePoint(position, normal, textureCoordinate);
        }

        private static Point ParsePoint(string args)
        {
            string[] numbers = args.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            double u, v;
            double.TryParse(numbers[0], NumberStyles.Float, _numberParseCulture, out u);
            double.TryParse(numbers[1], NumberStyles.Float, _numberParseCulture, out v);
            return new Point(u, 1.0 - v);
        }

        private static Vector3D ParseVector3D(string args)
        {
            string[] numbers = args.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            double x, y, z;
            double.TryParse(numbers[0], NumberStyles.Float, _numberParseCulture, out x);
            double.TryParse(numbers[1], NumberStyles.Float, _numberParseCulture, out y);
            double.TryParse(numbers[2], NumberStyles.Float, _numberParseCulture, out z);
            return new Vector3D(x, y, z);
        }

        private static Point3D ParsePoint3D(string args)
        {
            string[] numbers = args.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            double x, y, z;
            double.TryParse(numbers[0], NumberStyles.Float, _numberParseCulture, out x);
            double.TryParse(numbers[1], NumberStyles.Float, _numberParseCulture, out y);
            double.TryParse(numbers[2], NumberStyles.Float, _numberParseCulture, out z);
            return new Point3D(x, y, z);

        }

        static ObjImporter()
        {
            _numberParseCulture = new NumberFormatInfo();
            _numberParseCulture.NumberDecimalSeparator = ".";
            _numberParseCulture.NumberGroupSeparator = ",";
            _numberParseCulture.NumberNegativePattern = 1;
        }
        private static NumberFormatInfo _numberParseCulture = null;
    }
}
